#include <dalelib/graphics/md3.h>

namespace dalelib
{

v3 anorms[256][256];
bool md3_calulated_normals = false;

void MD3Model::transform(const m4 &_Mat)
{
	m4 notrans = _Mat.noTranslation();
	for(unsigned int i=0; i<_num_meshes; ++i)
	{
		MD3Mesh *m = &_meshes[i];

		for(unsigned int j=0; j<m->_num_vertices*m->_num_frames; ++j)
		{
			m->_frames[j]._pos = _Mat.transformPoint(m->_frames[j]._pos);
			m->_frames[j]._nrm = _Mat.transformVector(m->_frames[j]._nrm);
		}
	}
}

void MD3Model::calculateNormalTable()
{
	md3_calulated_normals = true;

	for(int j=0; j<256; ++j)
	{
		float beta = (float(j)*2.f*PI)/255;
		for(int i=0; i<256; ++i)
		{
			float alpha = (float(i)*2.f*PI)/255;
			float x = cosf(beta)*sinf(alpha);
			float y = sinf(beta)*sinf(alpha);
			float z = cosf(alpha);
			anorms[i][j] = v3(x,y,z);
		}
	}
}

int MD3Model::load(const string _FileName)
{
	if(!md3_calulated_normals) calculateNormalTable();

	ifstream fin( _FileName.c_str(), ios_base::in|ios_base::binary );
	if( !fin ) { cout << "can't open mesh file for loading" << endl; return -1; }

	struct
	{
		char id[4];			// id = 'IDP3'
		int  version;		// version == 15
		char filename[68];
		int  numBoneFrames;
		int  numTags;
		int  numMeshes;
		int  numMaxSkins;
		int  headerlength;
		int  tag_start;
		int  tag_end;
		int  filesize;
	} header;
	fin.read(reinterpret_cast<char*>(&header),sizeof(header));

	static char id[5];
	sprintf(id, "%c%c%c%c", header.id[0], header.id[1], header.id[2], header.id[3]);
//	sprintf_s(id, "%c%c%c%c", 5, header.id[0], header.id[1], header.id[2], header.id[3]);
	if( strcmp(id,"IDP3") || header.version!=MD3VERSION ) // first 4 bytes should be "IDP3" and next int should be 15
	{
		cout << _FileName << " is not a valid md3 file" << endl;
		return -1;
	}

	cout << "loading file: " << _FileName << endl;
	{
		_num_frames = header.numBoneFrames;
		BoneFrame *frames = new BoneFrame[header.numBoneFrames];
		fin.read((char*)frames, sizeof(BoneFrame)*header.numBoneFrames);
		delete [] frames;
	}
	{
		struct Tag { char name[64]; v3 position; float rotation[3][3]; };
		_num_links = header.numTags;
		_links = new MD3Link[_num_links*_num_frames];
		Tag *tags = new Tag[_num_links*_num_frames];
		fin.read((char*)tags, sizeof(Tag)*_num_links*_num_frames);
		for(unsigned int ii=0,j=0; j<1; ++j)
		{
			for(unsigned int i=0; i<_num_links; ++i,++ii)
			{
				_links[i]._model = 0;
				_links[i]._name = string(tags[ii].name);
/*
				_links[i]._mat =
					m4(
						1,0,0,0,
						0,1,0,0,
						0,0,1,0,
						tags[ii].position[0],    tags[ii].position[1],    tags[ii].position[2],    1 )*
					m4(
//						tags[ii].rotation[0][0], tags[ii].rotation[1][0], tags[ii].rotation[2][0], 0,
//						tags[ii].rotation[0][1], tags[ii].rotation[1][1], tags[ii].rotation[2][1], 0,
//						tags[ii].rotation[0][2], tags[ii].rotation[1][2], tags[ii].rotation[2][2], 0,
						tags[ii].rotation[0][0], tags[ii].rotation[0][1], tags[ii].rotation[0][2], 0,
						tags[ii].rotation[1][0], tags[ii].rotation[1][1], tags[ii].rotation[1][2], 0,
						tags[ii].rotation[2][0], tags[ii].rotation[2][1], tags[ii].rotation[2][2], 0,
						0,0,0,1 );
*/
				cout << "tag " << i << endl;
				cout << "   name " << string(tags[i].name) << endl;
				cout << "   position " << tags[ii].position[0] << ", " << tags[ii].position[1] << ", " << tags[ii].position[2] << endl;
			}
		}
		delete [] tags;
	}

	// *** load all meshes ***
	_num_meshes = header.numMeshes;
	_meshes = new MD3Mesh[_num_meshes];
	unsigned int mesh_offset = (unsigned int)fin.tellg();
	for(unsigned int i=0; i<_num_meshes; ++i)
	{

		struct
		{
			char id[4];
			char name[68];
			int  numMeshFrames;
			int  numSkins;
			int  numVertices;
			int  numTriangles;
			int  tri_start;
			int  headersize;
			int  texvec_start;
			int  vertex_start;
			int  meshsize;
		} meshheader;

		MD3Mesh *m = &_meshes[i];

		// Load the mesh's header
		fin.seekg(mesh_offset, ios_base::beg);
		fin.read((char*)&meshheader, sizeof(meshheader));

		m->_num_frames = meshheader.numMeshFrames;
		m->_num_triangles = meshheader.numTriangles;
		m->_num_vertices = meshheader.numVertices;
		m->_num_skins = meshheader.numSkins;

		// *** load skins ***
		m->_skins = new Skin[m->_num_skins];
		fin.seekg(mesh_offset+meshheader.headersize, ios_base::beg);
		fin.read((char*)m->_skins, sizeof(Skin)*m->_num_skins);

		// *** load mesh triangles ***
		m->_triangles = new Triangle[m->_num_triangles];
		fin.seekg(mesh_offset+meshheader.tri_start, ios_base::beg);
		fin.read((char*)m->_triangles, sizeof(Triangle)*m->_num_triangles);

		// *** load texture coordinates ***
		m->_texcoords = new TexCoord[meshheader.numVertices];
		fin.seekg(mesh_offset+meshheader.texvec_start, ios_base::beg);
		fin.read((char*)m->_texcoords, sizeof(TexCoord)*m->_num_vertices);

		// *** load vertices for all frames ***
		struct Vert { signed short Vec[3]; unsigned char Normal[2]; };
		Vert *verts = new Vert[m->_num_vertices*m->_num_frames];
		fin.seekg(mesh_offset+meshheader.vertex_start, ios_base::beg);
		fin.read((char*)verts, sizeof(Vert)*m->_num_vertices*m->_num_frames);
		m->_vertices = new Vertex[m->_num_vertices];
		m->_frames = new Vertex[m->_num_vertices*m->_num_frames];
		for(unsigned int i=0; i<m->_num_vertices*m->_num_frames; ++i)
		{
			m->_frames[i]._pos = v3(float(verts[i].Vec[0]),float(verts[i].Vec[1]),float(verts[i].Vec[2]))*(1.f/64);
//			cout << "vert " << verts[i].Vec[0] << ", " << verts[i].Vec[1] << ", " << verts[i].Vec[2] << endl;
			m->_frames[i]._nrm = anorms[verts[i].Normal[0]][verts[i].Normal[1]];
		}
		delete [] verts;

		mesh_offset += meshheader.meshsize;
	}
	fin.close();

	return 0;
}

int MD3Model::link(string _TagName, MD3Model *_Model)
{
	for(unsigned int i=0; i<_num_links; ++i)
	{
		if ( !_TagName.compare(_links[i]._name) )
		{
			_links[i]._model = _Model;
			return i;
		}
	}
	return -1;
}

void MD3Model::unlink(string _TagName)
{
	for(unsigned int i=0; i<_num_links; ++i)
	{
		if ( !_TagName.compare(_links[i]._name) )
		{
			_links[i]._model = 0;
			return;
		}
	}
}


void MD3Model::animate(const int _Start, const int _End, int &_Frame, float &_Lerp)
{
	if((_Frame<_Start) || (_Frame>_End)) _Frame = _Start;

	if( _Lerp>=1.f )
	{
		_Lerp -= 1.f;
		if(++_Frame>=_End) _Frame = _Start;
	}
	draw(_Frame,_Start+(_Frame-_Start+1)%(_End-_Start),_Lerp);
}

void MD3Model::draw(const unsigned int _Frame1,const unsigned int _Frame2,const float _Lerp)
{
	float llerp = 1.f-_Lerp;
	for(unsigned int i=0; i<_num_meshes; ++i)
	{
		MD3Mesh *m = &_meshes[i];
		Vertex *f1 = &m->_frames[_Frame1*m->_num_vertices];
		Vertex *f2 = &m->_frames[_Frame2*m->_num_vertices];
		Vertex *verts = m->_vertices;
		Triangle *tri = m->_triangles;
		TexCoord *tex = m->_texcoords;

		for(unsigned int j=0; j<m->_num_vertices; ++j)
		{
			verts[j]._pos = f1[j]._pos*llerp + f2[j]._pos*_Lerp;
			verts[j]._nrm = f1[j]._nrm*llerp + f2[j]._nrm*_Lerp;
		}

		glBegin(GL_TRIANGLES);
		for(unsigned int j=0; j<m->_num_triangles; ++j)
		{
			glTexCoord2fv((float*)tex[tri[j].v[0]]._uv.ptr());
			glNormal3fv((float*)verts[tri[j].v[0]]._nrm.ptr());
			glVertex3fv((float*)verts[tri[j].v[0]]._pos.ptr());
			glTexCoord2fv((float*)tex[tri[j].v[1]]._uv.ptr());
			glNormal3fv((float*)verts[tri[j].v[1]]._nrm.ptr());
			glVertex3fv((float*)verts[tri[j].v[1]]._pos.ptr());
			glTexCoord2fv((float*)tex[tri[j].v[2]]._uv.ptr());
			glNormal3fv((float*)verts[tri[j].v[2]]._nrm.ptr());
			glVertex3fv((float*)verts[tri[j].v[2]]._pos.ptr());
		}
		glEnd();
	}
}

void MD3Model::render()
{
	draw(0,0,0.f);
	for(unsigned int i=0; i<_num_links; i++)
	{
		if(_links[i]._model)
		{
			glPushMatrix();
			glMultMatrixf((float*)_links[i]._mat.ptr());
			_links[i]._model->render();
			glPopMatrix();

		}
	}
}

//*********************************************************************
void MD3Character::loadPlayer(string _Path, string _Skin)
{
	_head.load(_Path+string("head.md3"));
	_upper.load(_Path+string("upper.md3"));
	_lower.load(_Path+string("lower.md3"));

	_lower.link("tag_torso", &_upper);
	_upper.link("tag_head", &_head);
}

void MD3Character::transform(const m4 &_Mat)
{
	_head.transform(_Mat);
	_upper.transform(_Mat);
	_lower.transform(_Mat);
}

void MD3Character::render()
{
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glRotatef(-90,1,0,0);
//	glScalef(0.01f,0.01f,0.01f);
	_lower.render();
//	_upper.render(); // deleteme
	glPopMatrix();
}


} // namespace dalelib
